package models;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import models.msgs.GetCardsToExchange;
import models.msgs.GiveCards;
import models.msgs.GiveCoins;
import models.msgs.GiveStartCoins;
import models.msgs.InformActualBet;
import models.msgs.RetrieveBet;
import akka.actor.UntypedActor;

class Probability  {
	
	public static final double HighCard = 0.501;
	public static final double Pair = 0.423;
	public static final double TwoPairs = 0.0475;
	public static final double Three = 0.0211;
	public static final double Straight = 0.00392;
	public static final double Flush = 0.00197;
	public static final double Full = 0.00144;
	public static final double Four = 0.00024;
	public static final double StraightFlush = 0.0000154;
	
	private Probability(){
		//this prevents even the native class from 
		//calling this actor as well :
		throw new AssertionError();
	}
}

/**
 * Bot is created to play poker with 52 cards.
 * Each player gets 5 cards, which are used to create a hand
 * @author sdomagala
 */
public class BotStrategy extends UntypedActor {

	private List<Card> Cards;
	private List<Card> AllCards;
	private List<Double> Probability;
	private int coins;
	private int startCoins;
	private int tableBet;
	
	public BotStrategy()
	{
	}
	
	private double getProbabilityStraightFlush(List<Card> possibleCards, List<Card> holdCards)
	{
		int number = Cards.size();
		
		int figure = -1;
		int missing = number;
		int color = -1;
		
		boolean usedCards[][] = new boolean[4][13];
		Card usedCardsC[][] = new Card[4][13];
		for(Card c: Cards) {
			usedCards[c.getColor()][c.getFigure()] = true;
			usedCardsC[c.getColor()][c.getFigure()] = c;
		}
		
		for(Card card: Cards) {
			for(int i = -number + 1; i < number; i++) {
				if(card.getFigure() + i < 0 || card.getFigure()+i+number > 12)continue;
				
				int passed = 0;
				LinkedList<Card> holdCardsTemp = new LinkedList<Card>();
				
				for(int k = 0; k < number; k++) {
					if(usedCards[card.getColor()][card.getFigure()+i+k]) {
						passed++;
						holdCardsTemp.add(usedCardsC[card.getColor()][card.getFigure()+i+k]);
					}
				}
				
				if(number-passed <= missing && card.getFigure()+i+number-1 > figure)
				{
					missing = number-passed;
					figure = card.getFigure()+i+number-1;
					color = card.getColor();
					holdCards.clear();
					holdCards.addAll(holdCardsTemp);
				}
			}
		}
		
		List<Card> gcards = new LinkedList<Card>();
		List<List<Card>> groupOfCards = new LinkedList<List<Card>>();
		
		for(Card card: possibleCards) {
			if(card.getFigure() <= figure && card.getFigure() > figure-number
					&& card.getColor() == color && !usedCards[card.getColor()][card.getFigure()]) {
				gcards.add(card);
			}
		}
		
		for(int i = 0; i < missing; i++)
		{
			groupOfCards.add(gcards);
		}

		return getProbability(groupOfCards, possibleCards.size());
	}
	
	private double getProbabilityStraight(List<Card> possibleCards, List<Card> holdCards)
	{
		int number = Cards.size();
		
		int figure = -1;
		int missing = number;
		
		boolean usedFigures[] = new boolean[13];
		Card usedFiguresC[] = new Card[13];
		for(Card c: Cards) {
			usedFigures[c.getFigure()] = true;
			usedFiguresC[c.getFigure()] = c;
		}
		
		for(Card card: Cards) {
			for(int i = -number + 1; i < number; i++) {
				if(card.getFigure() + i < 0 || card.getFigure()+i+number-1 > 12)continue;
				
				int passed = 0;
				LinkedList<Card> holdCardsTemp = new LinkedList<Card>();
				
				for(int k = 0; k < number; k++) {
					if(usedFigures[card.getFigure()+i+k]) {
						passed++;
						holdCardsTemp.add(usedFiguresC[card.getFigure()+i+k]);
					}
				}
				
				if(number-passed <= missing && card.getFigure()+i+number-1 > figure)
				{
					missing = number-passed;
					figure = card.getFigure()+i+number-1;
					holdCards.clear();
					holdCards.addAll(holdCardsTemp);
				}
			}
		}
		
		List<Card> gcards = new LinkedList<Card>();
		List<List<Card>> groupOfCards = new LinkedList<List<Card>>();
		
		for(Card card: possibleCards) {
			if(card.getFigure() <= figure && card.getFigure() > figure-number && !usedFigures[card.getFigure()]) {
				gcards.add(card);
			}
		}
		
		for(int i = 0; i < missing; i++)
		{
			groupOfCards.add(gcards);
		}

		return getProbability(groupOfCards, possibleCards.size());
	}
	
	private double getProbabilityFlush(List<Card> possibleCards, List<Card> holdCards)
	{
		int number = Cards.size();
		
		int missing = number;
		int figure = -1;
		int color = 0;
		
		for(Card card: Cards) {
			int passed = 1;
			LinkedList<Card> holdCardsTemp = new LinkedList<Card>();
			holdCardsTemp.add(card);
			
			for(Card card2: Cards) {
				if(card == card2)continue;
				if(card2.getColor() == card.getColor()) {
					passed++;
					holdCardsTemp.add(card2);
				}
			}
			
			if(number-passed <= missing && card.getFigure() > figure)
			{
				missing = number - passed;
				figure = card.getFigure();
				color = card.getColor();
				holdCards.clear();
				holdCards.addAll(holdCardsTemp);
			}
		}
		
		List<Card> gcards = new LinkedList<Card>();
		for(Card c: possibleCards)
		{
			if(c.getColor() == color)gcards.add(c);
		}
		
		List<List<Card>> groupOfCards = new LinkedList<List<Card>>();
		
		for(int i = 0; i < missing; i++)
		{
			groupOfCards.add(gcards);
		}
		
		return getProbability(groupOfCards, possibleCards.size());
	}
	
	private double getProbabilityTheTwoTheSameCard(List<Card> possibleCards, List<Card> holdCards, int number1, int number2)
	{
		int figure1 = -1;
		int figure2 = -1;
		int missing = number1+number2;
		
		for(Card card: Cards)
		{
			int passed1 = 1;
			for(Card card2: Cards)
			{
				if(card2 == card)continue;
				
				int passed2 = 1;
				LinkedList<Card> holdCardsTemp = new LinkedList<Card>();
				holdCardsTemp.add(card);
				holdCardsTemp.add(card2);
				
				for(Card card3: Cards)
				{
					if(card == card3 || card2 == card3)continue;
					if(passed1 < number1 && card.getFigure() == card3.getFigure()) {
						passed1++;
						holdCardsTemp.add(card3);
					}
					if(passed2 < number2 && card2.getFigure() == card3.getFigure()) {
						passed2++;
						holdCardsTemp.add(card3);
					}
				}
				
				if(number1+number2-passed1-passed2 <= missing){
					missing = number1+number2-passed1-passed2;
					figure1 = card.getFigure();
					figure2 = card.getFigure();
					holdCards.clear();
					holdCards.addAll(holdCardsTemp);
				}
			}
		}
		
		List<List<Card>> groupOfCards = new LinkedList<List<Card>>();
		
		for(int i = 0; i < missing; i++)
		{
			List<Card> gcards = new LinkedList<Card>();
			
			for(Card c: possibleCards) {
				if(c.getFigure() == figure1 || c.getFigure() == figure2)
				{
					gcards.add(c);
				}
			}
			
			groupOfCards.add(gcards);
		}
		
		for(int i = 0; i < Cards.size()-(number1+number2); i++ )
		{
			groupOfCards.add(possibleCards);
		}
		
		return getProbability(groupOfCards, possibleCards.size());
	}
	
	private double getProbabilityTheSameCard(List<Card> possibleCards, List<Card> holdCards, int number) {
		int figure = -1;
		int missing = number;
		
		for(Card card: Cards)
		{
			int passed = 1;
			LinkedList<Card> holdCardsTemp = new LinkedList<Card>();
			holdCardsTemp.add(card);
			
			for(Card card2: Cards)
			{
				if(card2 == card)continue;
				if(card2.getFigure() == card.getFigure()) {
					passed++;
					holdCardsTemp.add(card2);
				}
			}
			
			if(number - passed <= missing)
			{
				missing = number - passed;
				figure = card.getFigure();
				holdCards.clear();
				holdCards.addAll(holdCardsTemp);
			}
		}
		
		List<List<Card>> groupOfCards = new LinkedList<List<Card>>();
		
		for(int i = 0; i < missing; i++)
		{
			List<Card> gcards = new LinkedList<Card>();
			
			for(Card c: possibleCards) {
				if(c.getFigure() == figure)
				{
					gcards.add(c);
				}
			}
			
			groupOfCards.add(gcards);
		}
		
		for(int i = 0; i < Cards.size()-number; i++ )
		{
			groupOfCards.add(possibleCards);
		}
		
		return getProbability(groupOfCards, possibleCards.size());
	}
	
	private double getProbability(List<List<Card>> groupsOfCards, int leftCards)
	{
		if(groupsOfCards.size() == 0) {
			throw new RuntimeException();
		}
		
		double [][] temporaryProbability = new double[4][13];
		
		for(int i = 0; i < 4; i++)
		{
			for(int j = 0; j < 13; j++)
			{
				temporaryProbability[i][j] = 1;
			}
		}
		
		double probability = 1;
		Iterator<List<Card>> groupIter = groupsOfCards.iterator();
		List<Card> currentGroup;
		while(groupIter.hasNext())
		{
			currentGroup = groupIter.next();
			if(currentGroup.size() == 0){
				throw new RuntimeException();
			}
			
			Iterator<Card> cardIter = currentGroup.iterator();
			Card currentCard;
			double partProb = 0;
			while(cardIter.hasNext())
			{
				currentCard = cardIter.next();
				partProb += temporaryProbability[currentCard.getColor()][currentCard.getFigure()]
						/leftCards;
				temporaryProbability[currentCard.getColor()][currentCard.getFigure()] -= 1.0/currentGroup.size();
				if(temporaryProbability[currentCard.getColor()][currentCard.getFigure()] < 0) {
					temporaryProbability[currentCard.getColor()][currentCard.getFigure()] = 0;
				}
			}
			probability *= partProb;
			
			leftCards--;
		};
		
		return probability;
	}
	
	private int silnia(int n) {
		for(int i = 1; i <= n-1; i++){
			n *= i;
		}
		return n;
	}
	
	public void giveCards(List<Card> cards) {
		Cards = cards;
		//TODO Uporz�dkowanie rosn�co
	}

	public List<Card> getCardsToExchange(int min, int max) {
		boolean usedCards[][] = new boolean[4][13];
		for(Card c: Cards) {
			usedCards[c.getColor()][c.getFigure()] = true;
		}
		
		List<Card> possibleCards = new LinkedList<Card>();
		for(int i = 0; i < 4; i++)
		{
			for(int j = 0; j < 13; j++)
			{
				if(!usedCards[i][j])possibleCards.add(new Card(i,j));
			}
		}
		
		Hand h = new Hand();
		for(Card c: Cards){
			h.giveCard(c);
		}
		
		List<Integer> value = h.getValue();
		double points;
		List<Card> cardsToExchange = new ArrayList<Card>();
		
		switch(value.get(0))
		{
			case 1:
				points = 1/models.Probability.Pair;
				break;
			case 2:
				points = 1/models.Probability.TwoPairs;
				break;
			case 3:
				points = 1/models.Probability.Three;
				break;
			case 4:
				points = 1/models.Probability.Straight;
				break;
			case 5:
				points = 1/models.Probability.Flush;
				break;
			case 6:
				points = 1/models.Probability.Full;
				break;
			case 7:
				points = 1/models.Probability.Four;
				break;
			case 9:
				points = 1/models.Probability.StraightFlush;
				break;
			default:
				points = 0;
		}
		
		List<Card> holdCards = new LinkedList<Card>();
		if(value.get(0) < 1)
		{
			double tempPoints = getProbabilityTheSameCard(possibleCards, holdCards, 2)/models.Probability.Pair;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		if(value.get(0) < 2)
		{
			double tempPoints = getProbabilityTheTwoTheSameCard(possibleCards, holdCards, 2, 2)/models.Probability.TwoPairs;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		if(value.get(0) < 3)
		{
			double tempPoints = getProbabilityTheSameCard(possibleCards, holdCards, 3)/models.Probability.Three;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		if(value.get(0) < 4)
		{
			double tempPoints = getProbabilityStraight(possibleCards, holdCards)/models.Probability.Straight;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		if(value.get(0) < 5)
		{
			double tempPoints = getProbabilityFlush(possibleCards, holdCards)/models.Probability.Flush;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		if(value.get(0) < 6)
		{
			double tempPoints = getProbabilityTheTwoTheSameCard(possibleCards, holdCards, 3, 2)/models.Probability.Full;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		if(value.get(0) < 7)
		{
			double tempPoints = getProbabilityTheSameCard(possibleCards, holdCards, 4)/models.Probability.Four;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		if(value.get(0) < 9)
		{
			double tempPoints = getProbabilityStraightFlush(possibleCards, holdCards)/models.Probability.StraightFlush;
			if(tempPoints > points)
			{
				points = tempPoints;
				cardsToExchange = new ArrayList<Card>(Cards);
				cardsToExchange.removeAll(holdCards);
			}
		}
		
		/*Random rnd = new Random();
		
		int cardsNumber = Math.abs(rnd.nextInt()) % (max-min+1) + min;
		
		List<Boolean> arrayList = new ArrayList<Boolean>();
		for(int i = 0; i < Cards.size(); i++) {
			arrayList.add(false);
		}
		
		for(int i = cardsNumber; i > 0 ; i--)
		{
			int number = 0;
			
			do {
				number = Math.abs(rnd.nextInt()) % cardsNumber;
			}while (arrayList.get(number));
			
			arrayList.set(number, true);
			cardsToExchange.add(Cards.get(number));
		}*/
		
		return cardsToExchange;
	}
	
	public void giveCoins(int coins){
		this.coins = coins;
	}
	
	public void giveStartCoins(int startCoins){
		this.startCoins = startCoins;
	}
	
	public int retrieveBet() {		
		Hand h = new Hand();
		for(Card c: Cards){
			h.giveCard(c);
		}
		
		List<Integer> value = h.getValue();
		if(coins < tableBet) return 0;
		if(tableBet == 0) return 10;
		
		if(value.get(0) < 2){   // high card, pair
			if(coins <= 0.75 * startCoins || tableBet > 0.15 * coins) return 0;
			coins -= tableBet;
			return tableBet;
		}
		else if(value.get(0) < 4){ //two pair, three of a kind
			if(coins <= 0.65 * startCoins || tableBet > 0.25 * coins) return 0;
			coins -= tableBet;
			return tableBet;
		}
		else if(value.get(0) < 7){ //straight, flush, full
			if(coins <= 0.5 * startCoins || tableBet > 0.5 * coins) return 0;
			coins -= (tableBet + coins/10);
			return tableBet + coins/10;
		}
		else { //four of a kind, straight flush
			coins -= (tableBet + coins/10);
			return tableBet + coins/10;
		}
	}
	
	public void informActualBet(int tableBet)
	{
		this.tableBet = tableBet;
	}

	@Override
    public void onReceive(Object msg) throws Exception
    {
    	if(msg instanceof RetrieveBet)
    	{
    		getSender().tell(retrieveBet(), getSelf());
    	}
    	else if(msg instanceof GiveCards)
    	{
    		giveCards(((GiveCards) msg).cards);
    	}
    	else if(msg instanceof GetCardsToExchange)
    	{
    		getSender().tell(getCardsToExchange(0, 4), getSelf());
    	}
    	else if (msg instanceof GiveCoins)
    	{
    		giveCoins(((GiveCoins) msg).coins);
    	}
    	else if (msg instanceof GiveStartCoins)
    	{
    		giveStartCoins(((GiveStartCoins) msg).coins);
    	}
    	else if (msg instanceof InformActualBet)
    	{
    		informActualBet(((InformActualBet) msg).coins);
    	}
    	else
    	{
    		unhandled(msg);
    	}
    }  

}
